﻿//-----------------------------------------------------------------------------------------
// <copyright file="MobilviAd.xaml.cs" company="Jakub Florczyk (www.jakubflorczyk.pl)">
//      Copyright © 2011 Jakub Florczyk (www.jakubflorczyk.pl)
//      http://mobilviwp7.codeplex.com
// </copyright>
//-----------------------------------------------------------------------------------------

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using Microsoft.Phone.Tasks;
using System.Net.NetworkInformation;
using ImageTools;
using ImageTools.IO.Gif;
using ImageTools.Helpers;

namespace MobilviWP7
{
    /// <summary>
    /// Advertisement control.
    /// </summary>
    public partial class MobilviAd : UserControl
    {
        #region Statics

        private static readonly Regex _regex = new Regex(@"<a.*href=[""'](?<href>[^""^']+[.]*)[""'].*><img.*src=[""'](?<src>[^""^']+[.]*)[""'].*/></a>", RegexOptions.IgnoreCase);

        #endregion

        #region Classes

        private class PostStateContainer
        {
            private HttpWebRequest _request;
            private byte[] _data;
            private AsyncCallback _callback;

            public HttpWebRequest Request
            {
                get { return _request; }
                set { _request = value; }
            }

            public byte[] Data
            {
                get { return _data; }
                set { _data = value; }
            }

            public AsyncCallback Callback
            {
                get { return _callback; }
                set { _callback = value; }
            }
        }

        #endregion

        #region Dependencies

        /// <summary>
        /// Identifies the MobilviWP7.MobilviAd.Site dependency property.
        /// </summary>
        public static readonly DependencyProperty SiteProperty = DependencyProperty.Register("Site", typeof(int), typeof(MobilviAd), new PropertyMetadata(0));

        /// <summary>
        /// Identifies the MobilviWP7.MobilviAd.Zone dependency property.
        /// </summary>
        public static readonly DependencyProperty ZoneProperty = DependencyProperty.Register("Zone", typeof(int), typeof(MobilviAd), new PropertyMetadata(0));

        /// <summary>
        /// Identifies the MobilviWP7.MobilviAd.MinSize dependency property.
        /// </summary>
        public static readonly DependencyProperty MinSizeProperty = DependencyProperty.Register("MinSize", typeof(Size), typeof(MobilviAd), new PropertyMetadata(new Size(300, 50)));

        /// <summary>
        /// Identifies the MobilviWP7.MobilviAd.MaxSize dependency property.
        /// </summary>
        public static readonly DependencyProperty MaxSizeProperty = DependencyProperty.Register("MaxSize", typeof(Size), typeof(MobilviAd), new PropertyMetadata(new Size(300, 50)));

        #endregion

        #region Properties

        /// <summary>
        /// Id of the publisher site.
        /// </summary>
        public int Site
        {
            get { return (int)GetValue(SiteProperty); }
            set { SetValue(SiteProperty, value); }
        }

        /// <summary>
        /// Id of the publisher zone.
        /// </summary>
        public int Zone
        {
            get { return (int)GetValue(ZoneProperty); }
            set { SetValue(ZoneProperty, value); }
        }

        /// <summary>
        /// Minimal size of the ad content to be shown.
        /// </summary>
        public Size MinSize
        {
            get { return (Size)GetValue(MinSizeProperty); }
            set { SetValue(MinSizeProperty, value); }
        }

        /// <summary>
        /// Maximal size of the ad content to be shown.
        /// </summary>
        public Size MaxSize
        {
            get { return (Size)GetValue(MaxSizeProperty); }
            set { SetValue(MaxSizeProperty, value); }
        }

        #endregion

        #region Events

        /// <summary>        
        /// Occurs when the ad received.
        /// </summary>
        public event RoutedEventHandler AdReceived;

        /// <summary>
        /// Occurs when the ad failed.
        /// </summary>
        public event RoutedEventHandler AdFailed;

        #endregion

        /// <summary>
        /// Initializes a new instance of the MobilviWP7.MobilviAd class.
        /// </summary>
        public MobilviAd()
        {
            InitializeComponent();
        }

        private void MobilviAd_Loaded(object sender, RoutedEventArgs e)
        {
            // pobranie reklamy
            Dispatcher.BeginInvoke(() =>
            {
                Request();
            });
        }

        private void MobilviAd_Unloaded(object sender, RoutedEventArgs e)
        {
            if (Image.Opacity == 1d)
                OutStoryboard.Begin();
        }

        #region Request

        private void Request()
        {
            if (NetworkInterface.GetIsNetworkAvailable())
            {
                // parameters
                StringBuilder parameters = new StringBuilder();
                parameters.Append("site=").Append(Site);
                parameters.Append("&zone=").Append(Zone);

                if (MinSize.Width > 0 && MinSize.Height > 0)
                {
                    parameters.Append("&min_size_x=").Append((int)MinSize.Width);
                    parameters.Append("&min_size_y=").Append((int)MinSize.Height);
                }

                if (MaxSize.Width > 0 && MaxSize.Height > 0)
                {
                    parameters.Append("&size_x=").Append((int)MaxSize.Width);
                    parameters.Append("&size_y=").Append((int)MaxSize.Height);
                }

                parameters.Append("&adstype=2");
                parameters.Append("&premium=0");
                parameters.Append("&key=1");
                parameters.Append("&count=1");
                parameters.Append("&size_required=true");

                // request
                HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("http://ads.mocean.mobi:80/ad");
                request.Method = "POST";
                request.ContentType = "application/x-www-form-urlencoded";

                try
                {
                    request.BeginGetRequestStream(new AsyncCallback((IAsyncResult asynchronousResult) =>
                    {
                        WriteRequestPost(asynchronousResult);
                    }), new PostStateContainer()
                    {
                        Request = request,
                        Data = Encoding.UTF8.GetBytes(parameters.ToString()),
                        Callback = new AsyncCallback((IAsyncResult asynchronousResult) =>
                        {
                            RequestCallback(ReadResponse(asynchronousResult));
                        })
                    });
                }
                catch
                {
                    RequestCallback(null);
                }
            }
            else
            {
                RequestCallback(null);
            }
        }

        private static void WriteRequestPost(IAsyncResult asynchronousResult)
        {
            PostStateContainer postStateContainer = (PostStateContainer)asynchronousResult.AsyncState;
            HttpWebRequest request = postStateContainer.Request;

            try
            {
                using (Stream postStream = request.EndGetRequestStream(asynchronousResult))
                {
                    postStream.Write(postStateContainer.Data, 0, postStateContainer.Data.Length);
                    postStream.Close();
                }
            }
            catch
            {
                return;
            }

            request.BeginGetResponse(postStateContainer.Callback, request);
        }

        private static string ReadResponse(IAsyncResult asynchronousResult)
        {
            HttpWebRequest request = (HttpWebRequest)asynchronousResult.AsyncState;
            HttpWebResponse response;

            try
            {
                response = (HttpWebResponse)request.EndGetResponse(asynchronousResult);
            }
            catch
            {
                return String.Empty;
            }

            // odczyt
            string s = null;
            if (response.StatusCode == HttpStatusCode.OK)
            {
                using (StreamReader streamReader = new StreamReader(response.GetResponseStream()))
                {
                    s = streamReader.ReadToEnd();
                    streamReader.Close();
                }
            }

            response.Close();
            request.Abort();
            response = null;
            request = null;

            return s;
        }

        private static MemoryStream ReadStreamResponse(IAsyncResult asynchronousResult)
        {
            HttpWebRequest request = (HttpWebRequest)asynchronousResult.AsyncState;
            HttpWebResponse response;

            try
            {
                response = (HttpWebResponse)request.EndGetResponse(asynchronousResult);
            }
            catch
            {
                return null;
            }

            // odczyt
            MemoryStream ms = null;
            if (response.StatusCode == HttpStatusCode.OK)
            {
                using (BinaryReader binaryReader = new BinaryReader(response.GetResponseStream()))
                {
                    ms = new MemoryStream(binaryReader.ReadBytes((int)binaryReader.BaseStream.Length));
                    binaryReader.Close();
                }
            }

            response.Close();
            request.Abort();
            response = null;
            request = null;

            return ms;
        }

        private void RequestCallback(string response)
        {
            Dispatcher.BeginInvoke(() =>
            {
                if (String.IsNullOrEmpty(response) || !_regex.IsMatch(response))
                {
                    OnAdFailed(new RoutedEventArgs());
                }
                else
                {
                    Match match = _regex.Match(response);
                    Image.Tag = new Uri(match.Groups["href"].Value, UriKind.Absolute);

                    // image request
                    try
                    {
                        HttpWebRequest imageRequest = (HttpWebRequest)HttpWebRequest.Create(match.Groups["src"].Value);

                        imageRequest.BeginGetResponse(new AsyncCallback((IAsyncResult asynchronousResult) =>
                        {
                            MemoryStream stream = ReadStreamResponse(asynchronousResult);
                            if (stream != null)
                            {
                                DecodeStream(stream);
                            }
                            else
                            {
                                OnAdFailed(new RoutedEventArgs());
                            }
                        }), imageRequest);
                    }
                    catch
                    {
                        RequestCallback(null);
                    }
                }
            });
        }

        private void DecodeStream(MemoryStream stream)
        {
            try
            {
                stream.Position = 0;

                if (stream.ReadByte() == 71 && stream.ReadByte() == 73 && stream.ReadByte() == 70) // bytes on 0,1,2 positions
                {
                    // gif
                    Dispatcher.BeginInvoke(delegate
                    {
                        try
                        {
                            stream.Position = 0;
                            ExtendedImage extendedImage = new ExtendedImage();
                            GifDecoder gifDecoder = new GifDecoder();
                            gifDecoder.Decode(extendedImage, stream);
                            Image.Source = ToBitmap(extendedImage);

                            ShowImage();
                        }
                        catch
                        {
                            OnAdFailed(new RoutedEventArgs());
                        }
                    });
                }
                else
                {
                    // other
                    Dispatcher.BeginInvoke(delegate
                    {
                        try
                        {
                            stream.Position = 0;                            
                            BitmapImage bi = new BitmapImage();
                            bi.SetSource(stream);
                            Image.Source = bi;

                            ShowImage();
                        }
                        catch
                        {
                            OnAdFailed(new RoutedEventArgs());
                        }
                    });
                }
            }
            catch
            {
                OnAdFailed(new RoutedEventArgs());
            }
        }

        private static WriteableBitmap ToBitmap(ImageBase image)
        {
            WriteableBitmap bitmap = new WriteableBitmap(image.PixelWidth, image.PixelHeight);
            ImageBase temp = image;
            byte[] pixels = temp.Pixels;
            int[] raster = bitmap.Pixels;
            Buffer.BlockCopy(pixels, 0, raster, 0, pixels.Length);
            for (int i = 0; i < raster.Length; i++)
            {
                int abgr = raster[i];
                int a = (abgr >> 24) & 0xff;
                float m = a / 255f;
                int argb = a << 24 |
                           (int)((abgr & 0xff) * m) << 16 |
                           (int)(((abgr >> 8) & 0xff) * m) << 8 |
                           (int)(((abgr >> 16) & 0xff) * m);
                raster[i] = argb;
            }
            bitmap.Invalidate();
            return bitmap;
        }

        private void ShowImage()
        {
            OnAdReceived(new RoutedEventArgs());

            InStoryboard.Begin();
        }

        #endregion

        #region External events

        private void Image_ImageFailed(object sender, ExceptionRoutedEventArgs e)
        {
            OnAdFailed(new RoutedEventArgs());
        }

        private void Image_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            new WebBrowserTask()
            {
                Uri = new Uri(((Image)sender).Tag.ToString())
            }.Show();
        }

        #endregion

        #region Events methods

        /// <summary>
        /// Called before the AdReceived event occurs.
        /// </summary>
        /// <param name="e">The data for the event.</param>
        protected virtual void OnAdReceived(RoutedEventArgs e)
        {
            if (AdReceived != null)
                AdReceived(this, e);
        }

        /// <summary>
        /// Called before the AdFailed event occurs.
        /// </summary>
        /// <param name="e">The data for the event.</param>
        protected virtual void OnAdFailed(RoutedEventArgs e)
        {
            if (AdFailed != null)
                AdFailed(this, e);
        }

        #endregion
    }
}
